/**
  *  Event Tracer (singleton class)
  *  (c) 2006 Seth Dillingham <seth.dillingham@gmail.com>
  *
  *  This software is hereby released into the public domain. Do with it as
  *  you please, but with the understanding that it is provided "AS IS" and
  *  without any warranty of any kind.
  *
  *  (But I'd love to be told about where and how this code is being used.)
  **/

/**
 * @class Event.Tracer
 *  Singleton Object: Event.Tracer
 *
 *  Description:
 *    provides some minimal event debugging/tracing/logging
 *    Event.Tracer is a sort of singleton
 *    Only use Event.Tracer for starting and stopping the event tracing
 *      service, and for acquiring a reference to the live tracing object
 *      (which is returned by both find_tracer and start_trace)
 *
 *  IMPORTANT
 *    You only need to use this file when debugging your custom events.
 *
 *  Requires:
 *    prototype.js
 *    the custom events services provided by event_mixins.js
 *
 *  Usage:
 *	   Start up event tracing by calling Event.Tracer.startTrace( viewport )
 *      viewport is a div (or the id of a div) on your page where trace
 *      output will be written
 *      returns: a reference to an instance of Event.Tracer.prototype
 *      (not the singleton)
 *    Kill the tracer (disable tracing in all publishers) with:
 *      Event.Tracer.stopTrace()
 *    Note that you can start the tracer (if it's not already running)
 *      and register your publishers, all in one step, with a call like:
 *      [publisher].toggleEventsTrace()
 */
Event.Tracer = Class.create();
Object.extend( Event.Tracer,
/** @scope Event.Tracer */
{
	_tracer: null,

	findTracer: function() {
		return ( this._tracer != undefined ) ? this._tracer : false;
	},

	startTrace: function( /*viewport*/ ) {
		if ( ! this._tracer )
			this._tracer = new Event.Tracer( /*viewport*/ );

		return this._tracer;
	},

	stopTrace: function() {
		if ( this._tracer ) {
			this._tracer.shutdown();

			delete this._tracer;

			this._tracer = null;

			return true;
		}

		return false;
	}
} );

/**
 *  Class Prototype: Event.Tracer.prototype
 *
 *  Description:
 *    acts as a "logger" for custom event publishers which
 *    are registered with the tracer
 *
 *    This code should generally be consider a black box: you shouldn't
 *    ever need to call it from your own code, because it's called
 *    automatically from any class which has been extended with
 *    Event.Publisher (after calling .toggleEventsTrace() to enable
 *    debugging on the publisher)
 *
 *  Requires:
 *    prototype.js
 *    the custom events services provided by event_mixins.js
 *
 *  Usage:
 *    Generally, you can activate the tracer from your event publisher
 *    by calling[publisher].toggleEventsTrace()
 */
Object.extend( Event.Tracer.prototype,
/** @scope Event.Tracer.prototype */
{
	_publishers: null,

	_viewport: null,

	_outputlist: null,

	initialize: function( /*viewport*/ ) {
		this._publishers = {};
		/*
		if ( viewport )
			viewport = $( viewport );
		if ( ! viewport ) {
			viewport = $( 'event_tracer_output' );

			if ( ! viewport )
				throw new Error( 'No view port specified, and none found.' );
		}

		this._viewport = viewport;

		var ls = $( 'event_tracer_output_list' );
		if ( ! ls ) {
			ls = document.createElement( 'UL' );
			ls.id = 'event_tracer_output_list';
			viewport.appendChild( ls );
		}
		this._outputlist = ls;
		*/
	},

	_shutdownPublisher: function( pair ) {
		this.unregisterPublisher( pair[ 1 ].publisher );
	},

	shutdown: function() {
		$H( this._publishers ).each( this._shutdownPublisher.bind( this ) );
		/*
		var li = document.createElement( 'LI' );
		li.innerHTML = '<b>Tracer shutdown.</b>';

		this._outputlist.insertBefore( li, this._outputlist.firstChild );*/
		Logger.info('Tracer shutdown.');
	},

	unregisterPublisher: function( event_publisher ) {
		this.stopListeningForEvent( event_publisher,
									'eventListenerAdded',
									true,
									'onEventListenerAdded' );
		this.stopListeningForEvent( event_publisher,
									'eventListenerRemoved',
									true,
									'onEventListenerRemoved' );
		this.stopListeningForEvent( event_publisher,
									'eventDispatched',
									true,
									'onEventDispatched' );

		delete this._publishers[ event_publisher.getEventSourceId() ];
	},

	registerPublisher: function( event_publisher ) {
		var id = event_publisher.getEventSourceId();
		var publisher_data = { id: id, publisher: event_publisher };

		this._publishers[ id ] = publisher_data;

		this.listenForEvent( event_publisher,
							 'eventListenerAdded',
							 true,
							 'onEventListenerAdded' );
		this.listenForEvent( event_publisher,
							 'eventListenerRemoved',
							 true,
							 'onEventListenerRemoved' );
		this.listenForEvent( event_publisher,
							 'eventDispatched',
							 true,
							 'onEventDispatched' );
	},

	traceEvent: function( evt ) {
		Logger.log('Event: ' + evt.event_data.event_name + ' : ' + evt.event_data);
		/*
		var li = document.createElement( 'LI' );
		var eventinfo = document.createElement( 'UL' );

		li.innerHTML = '<b>Event:</b> ' + evt.event_data.event_name;

		this._outputlist.insertBefore( li, this._outputlist.firstChild );

		li.appendChild( eventinfo );
		this._outputlist = eventinfo;

		$H( evt.event_data ).each( this._prettyprintObject.bind( this ) );

		this._outputlist = this._outputlist.parentNode.parentNode;*/
	},

	onEventListenerAdded: function( evt ) {
		this.traceEvent( evt );
	},

	onEventListenerRemoved: function( evt ) {
		this.traceEvent( evt );
	},

	onEventDispatched: function( evt ) {
		evt = evt.event_data.data;
		Logger.log('Event Dispatched:' + evt.event_data.event_name + ' : '+ evt.event_data);
		/*
		var li = document.createElement( 'LI' );
		var eventinfo = document.createElement( 'UL' );

		li.innerHTML = '<b>Event Dispatched:</b> <span class="event_name">' +
					   evt.event_data.event_name + '</span>';

		this._outputlist.insertBefore( li, this._outputlist.firstChild );

		li.appendChild( eventinfo );

		this._outputlist = eventinfo;

		$H( evt.event_data ).each( this._prettyprintObject.bind( this ) );

		this._outputlist = this._outputlist.parentNode.parentNode;*/
	},

	_prettyprintObject: function( pair ) {
		var li = document.createElement( 'LI' );

		li.innerHTML = '<span class="key">' + pair[ 0 ] + ':</span> ';
		if ( typeof pair[ 1 ] != "undefined" )
			li.innerHTML +=  '<span class="value">' +
							 pair[ 1 ].toString() + '</span>';

		this._outputlist.appendChild( li );

		switch ( pair[ 0 ] ) {
			case 'data':
			case 'event_data':
				var data = document.createElement( 'UL' );

				li.appendChild( data );

				this._outputlist = data;

				$H( pair[ 1 ] ).each( this._prettyprintObject.bind( this ) );

				this._outputlist = this._outputlist.parentNode.parentNode;

			default:
				if ( pair[ 1 ] && pair[ 1 ].tagName )
					li.innerHTML = '<span class="key">' +
								   pair[ 0 ] +
								   ':</span> <span class="value">' +
								   pair[ 1 ].tagName +
								   '</span>';
		}
	}
} );
Object.extend( Event.Tracer.prototype, Event.Listener );
